#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <stdexcept>
#include <cstdlib>
#include <boost/program_options/cmdline.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/regex.hpp>
#include <boost/any.hpp>

#include "Constants.hpp"
#include "Config.hpp"
#include "Misc.hpp"

Config::Config(int argc, char **argv) : _argc(argc), _argv(argv) {
	config_file = CONFIG_DEFAULT_FILE;
}

Config::~Config() {
}

bool Config::loadConfigFile(const std::string &filename) throw(ConfigErrorCmdLine, ConfigErrorConfigFile) {
	config_file = filename;
	return loadConfig();
}

bool Config::loadConfig() throw(ConfigErrorCmdLine, ConfigErrorConfigFile) {
	namespace po = boost::program_options;
	using std::vector;
	using std::string;

	po::options_description desc("Options " APP_VERSION);
	// Command-line (and config file) options
	desc.add_options()
		//http://www.boost.org/doc/libs/1_51_0/doc/html/boost/program_options/typed_value.html#id1169436-bb
		("help,h", "Produce this help message")
		("tolerant,t", po::bool_switch(), "Tolerant config parsing (ignore unknown keys)")
// 		("IsDevServer,d", po::bool_switch(), "We are runnig on dev server")
// 		("GCTime,g", po::value<int>()->default_value(CONFIG_DEFAULT_GCTIME), "Garbage collection interval (sec)")
		;

    try {
		po::store( po::parse_command_line(_argc, _argv, desc), vm);
	}catch(std::exception &e){
		throw ConfigErrorCmdLine(e);
	}

	// Print usage if needed and exit
	if( vm.count("help") ) {
		std::cout << desc << "\n";
		return false;
	}

	// Config file only options
	desc.add_options()
 		("MysqlAuthServer", po::value< typename misc::IpAddrList >()->multitoken()->required(), "Mysql server(s) to connect for user auth")
		;
	
	try {
		std::ifstream fp( config_file.c_str() );
		po::store( po::parse_config_file(fp, desc, getValue_Cast< bool >("tolerant")), vm);
    }catch(std::exception &e){
		throw ConfigErrorConfigFile(e);
    }
    po::notify(vm);

	// Fill some most used variables
	_MysqlAuthServers = getValue_Cast< typename misc::IpAddrList >("MysqlAuthServer");

	return true;
}

namespace misc { // This overload for >> to fill misc::IpAddr objects from boost's validator
std::istream& operator>>(std::istream& in, misc::IpAddr& unit) {
    using namespace boost;
    static regex r("^\\s*((\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))(\\:(\\d+))?\\s*$");
	//                   12        3        4        5      6   7

	std::string s;
	in >> s;

	smatch match;
	if( regex_match(s, match, r) ) {
		// Check ip addr
		for(int oct=2; oct<6; oct++) {
			int num = atoi(match[oct].str().c_str());
			if( num < 0 || num>255 ) throw ConfigErrorValidate( std::string("Invalid IP address: ")+s );
		}
		int port = atol(match[7].str().c_str());
		if( port<1 || port>65535 ) throw ConfigErrorValidate( std::string("Invalid port: ")+s );
		unit.setAddr( std::string(match[1]) );
		unit.setPort( port );
	} else {
		throw ConfigErrorValidate( std::string("Invalid IP address/port: ")+s );
	}
	return in;
}
};

